//*****************************************************************
// IOThreadGroup.cpp
//
//  Created on: Jan 21, 2012
//      Author: Christopher Wood
//              Robert Livosi
//
//  $Id: IOThreadGroup.cpp 23 2012-01-28 02:51:05Z caw4567@gmail.com $
//*****************************************************************

#include "IOThreadGroup.h"

/**
 * The (friendly) run function for the analog input thread.
 *
 * @param arg - pointer to the host IOThreadGroup object.
 */
void* inputThreadRun(void* arg);

/**
 * The (friendly) run function for the analog output thread.
 *
 * @param arg - pointer to the host IOThreadGroup object.
 */
void* outputThreadRun(void* arg);

/**
 * The sampling timer callback (used to trigger an analog input read).
 *
 * @param arg - struct that contains a pointer to the host IOThreadGroup object.
 */
void timerExpired(sigval arg);

/**
 * Default constructor for the IOThreadGroup that initializes the
 * hardware necessary to perform analog I/O.
 */
IOThreadGroup::IOThreadGroup()
{
	// Get a handle to the DIO control register
	dioControlHandle = mmap_device_io(PORT_LENGTH, DIO_CTRL_ADDRESS);

	// Configure the DIO channels according to the protocol
	// A-output (A/D values), B-output (done bit), C-output (comm status)
	out8(dioControlHandle, 0x00);

	// Get a handle to the LSB of the A/D register
	lsbHandle = mmap_device_io( PORT_LENGTH, LSB_BASE_ADDRESS );

	// Get a handle to the MSB of the A/D register
	msbHandle = mmap_device_io( PORT_LENGTH, MSB_BASE_ADDRESS );

	// Get a handle to the A/D channel register
	adChannelHandle = mmap_device_io( PORT_LENGTH, AD_CHANNEL_ADDRESS );

	// Set the board to the specified channel(s)
	out8(adChannelHandle, 0x00);

	// Get a handle to the analog input status register
	analogInputStatusHandle = mmap_device_io( PORT_LENGTH, ANALOG_INPUT_STATUS_ADDRESS );
	ainteHandle = mmap_device_io(PORT_LENGTH, ANALOG_INPUT_STATUS_ADDRESS + 1);
	fifoHandle = mmap_device_io(PORT_LENGTH, ANALOG_INPUT_STATUS_ADDRESS + 2);
	basep1Handle = mmap_device_io(PORT_LENGTH, 0x280);

	// Set A/D FIFO threshold to 1
	out8(fifoHandle, 0xFF);

	// Set AINTE to 0
	out8(ainteHandle, 0x00);

	// Select the input range for the A/D converter
	out8( analogInputStatusHandle, ANALOG_INPUT_STATUS_VAL );	// +10V, -10V range (gain of 0)

	// Create the input/output execution semaphores
	sem_init(&inputThreadSem, 0, 0);
	sem_init(&outputThreadSem, 0, 0);

	// Initialize the transform input/output buffer and their indices
	inputPutIndex = 0;
	outputPutIndex = 0;
	inputGetIndex = 0;
	outputGetIndex = 0;
	memset(inputBuffer, 0, sizeof(float) * BUFFER_SIZE);
	memset(outputBuffer, 0, sizeof(float) * BUFFER_SIZE);
}

/**
 * Default destructor (does nothing).
 */
IOThreadGroup::~IOThreadGroup()
{
}

/**
 * Start the input thread.
 */
void IOThreadGroup::runInputThread()
{
	inputThreadAlive = true;
	pthread_create(&inputThreadID, NULL, inputThreadRun, this);
}

/**
 * Start the output thread.
 */
void IOThreadGroup::runOutputThread()
{
	outputThreadAlive = true;
	pthread_create(&outputThreadID, NULL, outputThreadRun, this);
}

/**
 * The sampling timer callback (used to trigger an analog input read).
 *
 * @param arg - struct that contains a pointer to the host IOThreadGroup object.
 */
void timerExpired(sigval arg)
{
	IOThreadGroup* master = (IOThreadGroup*)(arg.sival_ptr);
	master->signalInputData();
}

/**
 * The (friendly) run function for the analog input thread.
 *
 * @param arg - pointer to the host IOThreadGroup object.
 */
void* inputThreadRun(void* arg)
{
	IOThreadGroup* master = (IOThreadGroup*)arg;
	int16_t adReturnValue;
	float adInput;
	struct itimerspec timerSpec;
	struct sigevent event;
	timer_t timerID;

	// Initialize the daemon timer thread to invoke a function when it expires
	SIGEV_THREAD_INIT(&event, &timerExpired, master, 0);

	// Configure the timer parameters (period signals only)
	// TODO: get rid of the magic numbers
	timerSpec.it_value.tv_sec = 0;
	timerSpec.it_value.tv_nsec = 100000000; // 10Hz
	timerSpec.it_interval.tv_sec = 0;
	timerSpec.it_interval.tv_nsec = 100000000; // 10Hz

	// Initialize a timer to control the transform process
	timer_create(CLOCK_REALTIME, &event, &timerID);
	timer_settime(timerID, 0, &timerSpec, NULL);

	// Jump into the main execution loop
	while (master->isInputThreadAlive())
	{
		// Wait for an input data event
		sem_wait(master->getInputThreadSem());

		// Perform the A/D conversion from analog input to floating point value
		master->performADConversion();
		adReturnValue = master->readADData();
		adInput = master->convertAnalogToFloat(adReturnValue);

		// Place A/D input value into the buffer and signal a transform
		master->setInputValue(adInput);
		sem_post((master->getTransformer())->getTransformSem());
	}

	return NULL;
}

/**
 * The (friendly) run function for the analog output thread.
 *
 * @param arg - pointer to the host IOThreadGroup object.
 */
void* outputThreadRun(void* arg)
{
	IOThreadGroup* master = (IOThreadGroup*)arg;
	float output = 0.0;
	uint16_t daCode = 0;
	uint8_t LSB = 0;
	uint8_t MSB = 0;
	uint8_t channel = 0;

	while (master->isOutputThreadAlive())
	{
		// Wait for an output data event
		sem_wait(master->getOutputThreadSem());

		// Grab the most recent transform value
		output = master->getOutputValue();

		// Perform the appropriate conversion
		daCode = master->convertFloatToAnalog(output);

		// Strip off the low 8 bits, keep 4 high bits
		LSB = daCode & 255;
		MSB = (daCode >> 8) & 15;

		// Select Channel 0
		channel = 0;
		out8(IOThreadGroup::DAC_LSB, LSB);
		out8(IOThreadGroup::DAC_MSB_Channel, MSB + (channel << 6));

		// Wait for the D/A to update
		while (in8(IOThreadGroup::DAC_ANALOG_INPUT_STATUS) & IOThreadGroup::WAIT_DAC);
	}

	return NULL;
}

/**
 * Release the input thread from its semaphore.
 */
void IOThreadGroup::releaseInputThread()
{
	sem_post(&inputThreadSem);
}

/**
 * Release the output thread from its semaphore.
 */
void IOThreadGroup::releaseOutputThread()
{
	sem_post(&outputThreadSem);
}

/**
 * Retrieve the current transformer object used by the controller.
 *
 * @return a pointer to the current TransformThread object.
 */
TransformThread* IOThreadGroup::getTransformer()
{
	return transformThread;
}

/**
 * Retrieve the next value from the transform thread's list.
 *
 * @return most recent input value.
 */
float IOThreadGroup::getInputValue()
{
	float val = inputBuffer[inputGetIndex];
	inputGetIndex = (inputGetIndex + 1) % IOThreadGroup::BUFFER_SIZE;
	return val;
}

/**
 * Retrieve the next value from the output thread's list.
 *
 * @return most recent output value.
 */
float IOThreadGroup::getOutputValue()
{
	float val = outputBuffer[outputGetIndex];
	outputGetIndex = (outputGetIndex + 1) % IOThreadGroup::BUFFER_SIZE;
	return val;
}

/**
 * Add an input value to the transform object's list.
 *
 * @val - new value to add to the input list.
 */
void IOThreadGroup::setInputValue(float val)
{
	inputBuffer[inputPutIndex] = val;
	inputPutIndex = (inputPutIndex + 1) % IOThreadGroup::BUFFER_SIZE;
}

/**
 * Add a converted value to the output thread's list.
 *
 * @val - new value to add to the output list.
 */
void IOThreadGroup::setOutputValue(float val)
{
	outputBuffer[outputPutIndex] = val;
	outputPutIndex = (outputPutIndex + 1) % IOThreadGroup::BUFFER_SIZE;
}

/**
 * Assign a transform thread to this I/O group.
 *
 * @param tThread - pointer to the transform thread.
 */
void IOThreadGroup::assignTransform(TransformThread* tThread)
{
	this->transformThread = tThread;
}

/**
 * Assign a user control thread to this I/O group.
 *
 * @param cThread - pointer to the user control thread.
 */
void IOThreadGroup::assignControl(ControlThread* cThread)
{
	this->controlThread = cThread;
}

/**
 * Indicate that a new A/D sample should be performed.
 */
void IOThreadGroup::signalInputData()
{
	sem_post(&inputThreadSem);
}

/**
 * Determine if the analog input thread is still alive.
 *
 * @return true if alive, false otherwise.
 */
bool IOThreadGroup::isInputThreadAlive()
{
	return inputThreadAlive;
}

/**
 * Determine if the analog output thread is still alive.
 *
 * @return true if alive, false otherwise.
 */
bool IOThreadGroup::isOutputThreadAlive()
{
	return outputThreadAlive;
}

/**
 * Set the state of the analog input thread.
 *
 * @param alive - new thread state.
 */
void IOThreadGroup::setInputThreadState(bool alive)
{
	inputThreadAlive = alive;
}

/**
 * Set the state of the analog output thread.
 *
 * @param alive - new thread state.
 */
void IOThreadGroup::setOutputThreadState(bool alive)
{
	outputThreadAlive = alive;
}

/**
 * Get a pointer to the semaphore controlling the intput thread.
 *
 * @return pointer to input task semaphore.
 */
sem_t* IOThreadGroup::getInputThreadSem()
{
	return &inputThreadSem;
}

/**
 * Get a pointer to the semaphore controlling the output thread.
 *
 * @return pointer to output task semaphore.
 */
sem_t* IOThreadGroup::getOutputThreadSem()
{
	return &outputThreadSem;
}

/**
 * Perform an A/D conversion.
 */
void IOThreadGroup::performADConversion(void)
{
	while ((in8(analogInputStatusHandle) & ANALOG_INPUT_WAIT_BIT) != 0)
	{
	}

	// Start the A/D conversion and flush the buffer
	out8(lsbHandle, 0x10);
	out8(lsbHandle, BASE_OUTPUT_VAL);

	while ((in8(analogInputStatusHandle) & BASE_OUTPUT_VAL) != 0)
	{
	}
}

/**
 * Read the A/D data from the hardware.
 *
 * @return analog signal value.
 */
int16_t IOThreadGroup::readADData(void)
{
	int8_t lsbData = in8(lsbHandle);
	int8_t msbData = in8(msbHandle);
	int16_t result = ((int16_t)(msbData << 8) + lsbData);
	return result;
}

/**
 * Convert an analog signal value to its floating point alternative.
 *
 * @param adBoardData - the analog signal value.
 * @return the converted floating point value.
 */
float IOThreadGroup::convertAnalogToFloat(int16_t adBoardData)
{
	double convertedInputVoltage = (double)(adBoardData * INPUT_RANGE) / AD_RANGE;
	return convertedInputVoltage;
}

/**
 * Convert a floating point voltage level to an analog signal.
 *
 * @param adData - the desired analog voltage.
 * @return the converted voltage level.
 */
int16_t IOThreadGroup::convertFloatToAnalog(float adData)
{
	return(int)(((adData / INPUT_RANGE) * 2048) + 2048);
}
